/* Copyright (C) 
 * 2012 - marco
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 * 
 */

#include "exChar.h"

/**
 * @brief exchar_open
 * 	 open device need call this function.
 *
 * @param inode 我不知道这个参数是干嘛的.
 * @param file  注意这个结构.在include/linux/fs.h中定义.
 *
 * @return 
 */
static int exchar_open(struct inode *inode, struct file *file) {
	/* 这个是样例.申请一个结构.
	 * 用于后续的操作.
	 */
    cmd_t  *op;
    op = (cmd_t*)kmalloc(sizeof(cmd_t), GFP_KERNEL);
    if (IS_ERR(op)) {
		printk(1, "cant alloc an op_target struct\r\n");
        return -1;
    }
	op->cmd = 'c';
	op->bank = 'b';
	op->bit = 0;
	op->val = 1;
    file->private_data = op;
    return 0;
}


/**
 * @brief exchar_release
 * 	close device need call this function.
 *
 * @param inode
 * @param file
 *
 * @return 
 */
static int exchar_release(struct inode *inode, struct file *file)
{
	/* 释放OPEN时申请的空间.*/
    cmd_t *op = file->private_data;

    if (op) {
        kfree(op);
    }
    return 0;
}

/**
 * @brief exchar_read
 * 	read device need call this function.
 *
 * @param file file指针.
 * @param buf 用户空间指针.
 * @param size BUF长度.
 * @param 
 *
 * @return 
 */
static ssize_t exchar_read(struct file *file, char __user *buf, size_t size, loff_t *o) {
    cmd_t  *op = file->private_data;
    char  tmp[10];
    char  cmd[24];
    int  val;

    if (buf == NULL || op == NULL) {
        return -1;
    }
    copy_from_user(tmp, buf, size);
	sprintf(cmd, "READ:[%s][%c][%c][%d][%d]", tmp, op->cmd, op->bank, op->bit, op->val);
	printk(1, "CMD:%s", cmd);
    return copy_to_user(buf, cmd, strlen(cmd));
}

/**
 * @brief 
 *
 * @param file
 * @param buf
 * @param bytes
 * @param off
 *
 * @return 
 */
static ssize_t exchar_write(struct file *file, const char __user *buf, size_t bytes, loff_t *off) {
    cmd_t  *op = file->private_data;
    char  tmp[10];
    char  cmd[10];
    int  val;

    if (buf == NULL || op == NULL) {
        return -1;
    }
    copy_from_user(tmp, buf, bytes);
//  spin_lock(&exchar_lock);
	sprintf(cmd, "Write:[%s][%c][%c][%d][%d]", tmp, op->cmd, op->bank, op->bit, op->val);
//	spin_unlock(&exchar_lock);

	printk(1, "CMD:%s", cmd);
    return strlen(cmd);
}


/**
 * @brief exchar_ioctl
 * 	ioctl device need call this function.
 *
 * @param inode
 * @param file 
 * @param ctl_cmd  用户IOCTL的参数.
 * @param arg
 *
 * @return 
 */
static int exchar_ioctl(struct inode *inode, struct file *file,
                         unsigned int ctl_cmd, unsigned long arg)
{
    cmd_t *op = file->private_data;
    char tmp[10];
    char cmd[10];

    switch (ctl_cmd) {
    case CMD_OP:
        if (copy_from_user(tmp, (cmd_t*)arg, sizeof(cmd_t))) {
            return -EFAULT;
        }
        sprintf(cmd, "IOCTL:%c:%c:%d:%d", op->cmd, op->bank, op->bit, op->val);
		printk(1, "CMD:%s", cmd);
        if (copy_to_user((cmd_t*)arg, op, sizeof(cmd_t))) {
            return  -EFAULT;
        }
        break;
    default:
        break;
    }
    return 0;
}

static const struct file_operations exchar_fops = {
    .open   = exchar_open,
    .read   = exchar_read,
    .write  = exchar_write,
    .unlocked_ioctl  = exchar_ioctl,
    .release  = exchar_release,
    .poll = NULL,
};

int  create_exchar_device(exchar_t *exchar)
{
    int ret ;

    ret = class_register(&exchar_class);
    if (ret < 0) {
		printk(1, "error create exchar class\r\n");
        return ret;
    }
    exchar->cla = &exchar_class ;
    exchar->dev = device_create(exchar->cla, NULL, MKDEV(exchar->major, 0), NULL, strcat(exchar->name, "_dev"));
    if (IS_ERR(exchar->dev)) {
        printk(1, "create exchar device error\n");
        class_unregister(exchar->cla);
        exchar->cla = NULL;
        exchar->dev = NULL;
        return -1 ;
    }

    if (uio_register_device(exchar->dev, &exchar_uio_info)) {
        printk(1, "exchar UIO device register fail.\n");
        return -1;
    }

    printk(1, "create exchar device success\n");
    return  0;
}

/*****************************************************************
**  module   entry and exit port
******************************************************************/
static int __init exchar_init_module(void)
{
    sprintf(exchar.name, "%s", EXCHAR_DEVICE_NAME);
    exchar.major = register_chrdev(0, EXCHAR_DEVICE_NAME, &exchar_fops);
    if (exchar.major < 0) {
		printk(1, "register example char dev error\r\n");
    } else {
		printk(1, "register example char dev major number:[%d]\r\n", exchar.major);
        if (0 > create_exchar_device(&exchar)) {
            unregister_chrdev(exchar.major, exchar.name);
            exchar.major = -1;
        }
    }

    return exchar.major;
}
static __exit void exchar_remove_module(void)
{
    if (0 > exchar.major) {
        return ;
    }
    uio_unregister_device(&exchar_uio_info);
    device_destroy(exchar.cla, MKDEV(exchar.major, 0));
    class_unregister(exchar.cla);
    return ;
}
/****************************************/
module_init(exchar_init_module);
module_exit(exchar_remove_module);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("MELE char test driver");
MODULE_AUTHOR("marco <marco@mele.cn>");

